﻿// anip.cpp : Defines the entry point for the application.
//

#include "anip.h"
#include "audio/primitives.h"
#include "vg/primitives.h"
#include "shared.h"
#include <map>
#include <codecvt>

using namespace std;
using namespace anip;

const float FPS = 48;

Font font = Font::null;
float currentY = 0.8f, dialogY = 0.0f;
float offset = 0, offsetDialog = 0.0f;
int dialogFrame = 0, frame = 0, dialogTime = 0;
vector<void*> recyclable;

float presentationSpeed = 0.07f;
int monitor = -1;

struct ElemPref
{
	bool over = false, under = false, fade = false, manual = false;
	enum { None, Width, Height, Fill, Auto } fit = Auto;
	vec2 loc = { 0.5, 0.5 };
	vec2 pos, size;
	int spanning = 1;
} pref;

struct UserHead
{
	wstring name;
	color color;
};

map<wchar_t, UserHead> heads;

Sequence balloons(FPS), dialogues(FPS);
vg::Embedding balloonsP(&balloons), dialoguesP(&dialogues);
FrameGroup composite{ &balloonsP, &dialoguesP };
Viewpoint view;

Texture2D* createShared(string path)
{
	Texture2D* t = Texture2D::createFromFile(path);
	Shared::add(*t);
	return t;
}

void createBalloon(wstring msg, wstring uname, color head, int mag = 0)
{
	const float width = 0.7;
	const float userSize = 0.03;
	const float userSpacing = 0.02;
	const float iconSize = 0.09;
	const float iconSpacing = 0.03;
	const float margin = 0.02;
	const float tsize = 0.05;
	const float tmaxWidth = width - margin * 2;
	const float round = 0.01;
	const float spacing = 0.03;

	float oldY = currentY;

	auto icon = new vg::SimpleShape(vg::SimpleShape::Type::RoundedSquare);
	icon->position = vec2{ 0.1, currentY };
	icon->size = vec2{ iconSize, iconSize };
	icon->color = head;
	icon->parameter = round;

	auto user = new vg::Text(font, uname);
	user->lineSize = userSize;
	user->maxWidth = width - margin * 2;
	user->position = vec2{ 0.1 + iconSize + iconSpacing, currentY };
	user->color = Colors::white;

	currentY += userSize + userSpacing;

	auto text = new vg::Text(font, msg);
	text->lineSize = tsize;
	text->maxWidth = tmaxWidth;
	vec2 size = text->estimateSize();
	text->position = vec2{ 0.1 + iconSize + iconSpacing + margin, currentY + margin };

	auto back = new vg::SimpleShape(vg::SimpleShape::Type::RoundedSquare);
	back->position = vec2{ 0.1 + iconSize + iconSpacing, currentY };
	back->size = vec2{ size.x + margin * 3, size.y + margin * 2 };
	back->color = Colors::lightSkyBlue;
	back->parameter = round;

	auto back2 = new vg::SimpleShape(vg::SimpleShape::Type::RoundedSquare);
	back2->position = vec2{ 0.1 + iconSize + iconSpacing, currentY };
	back2->size = vec2{ round * 2, round * 2};
	back2->color = Colors::lightSkyBlue;
	back2->parameter = round / 3;

	auto group = new FrameGroup{ icon, user, back, back2, text };
	balloons.addGroup(*group, frame);
	currentY += size.y + margin * 2 + spacing;
	float newOffset = offset - (currentY - oldY);

	group->position = vec2{ -1, 0 };
	group->position.addKey({ 0, {-1.0f, 0.0f} });
	group->position.addKey({ 1, {0.0f, 0.0f} });

	group->setDuration(max(15, mag + 3));

	balloons.position.addKey({ (float)frame, { 0.0f, offset } });
	balloons.position.addKey({ frame + 0.4f, { 0.0f, newOffset } });

	if (mag > 0)
	{
		mag += 2;
		back->color.keepLastKey(1);
		back->color.addKey({ 2.0f, {0.8, 0.8, 0.8, 1} });
		back->color.keepLastKey(mag);
		back->color.addKey({ 1.0f + mag, back->color[0] });

		back->position.keepLastKey(1);
		back->position.addKey({ 2.0f, {0.0f, -newOffset} });
		back->position.keepLastKey(mag);
		back->position.addKey({ 1.0f + mag, back->position[0] });

		back->size.keepLastKey(1);
		back->size.addKey({ 2.0f, {view.aspectRatio, 1} });
		back->size.keepLastKey(0 + mag);
		back->size.addKey({ 1.0f + mag, back->size[0] });

		back2->color.keepLastKey(1.0f);
		back2->color.addKey({ 2.0f, {0.8, 0.8, 0.8, 1} });
		back2->color.keepLastKey(mag);
		back2->color.addKey({ 1.0f + mag, back2->color[0] });

		text->color.keepLastKey(1.0f);
		text->color.addKey({ 2.0f, {1, 1, 1, 0} });
		text->color.keepLastKey(mag);
		text->color.addKey({ 1.0f + mag, text->color[0] });

		text->lineSize.keepLastKey(1);
		text->lineSize.addKey({ 2.0f, tsize * 3 });
		text->lineSize.keepLastKey(mag);
		text->lineSize.addKey({ 1.0f + mag, text->lineSize[0] });

		text->maxWidth.keepLastKey(1);
		text->maxWidth.addKey({ 2.0f, tmaxWidth * 3 });
		text->maxWidth.keepLastKey(mag);
		text->maxWidth.addKey({ 1.0f + mag, text->maxWidth[0] });

		dialogFrame = frame + 1;
		dialogTime = mag;
		frame += mag;
	}

	offset = newOffset;
	frame++;
	
	recyclable.push_back(icon);
	recyclable.push_back(text);
	recyclable.push_back(back);
	recyclable.push_back(back2);
	recyclable.push_back(group);
}

FrameGroup* createDialogue(wstring msg, color col)
{
	const float tsize = 0.08;
	const float xpos = 0.2;
	const float width = view.aspectRatio - 2 * xpos;
	const float spacing = 0.05;

	auto text = new vg::Text(font, msg, { xpos, dialogY });
	text->maxWidth = width;
	text->color = color{ col.r, col.g, col.b, 0 };
	text->color.addKey({ 1, {col.r, col.g, col.b, 1} });
	vec2 size = text->estimateSize();

	auto group = new FrameGroup{ text };
	group->setDuration(min(5, dialogTime));
	dialogues.addGroup(*group, dialogFrame);
	
	dialogues.position.addKey({ (float)dialogFrame, {0, offsetDialog + 0.5f} });
	offsetDialog = -dialogY - size.y * 0.5f;
	dialogues.position.addKey({ dialogFrame + 1.0f, {0, offsetDialog + 0.5f} });

	dialogY += size.y + spacing;

	recyclable.push_back(text);
	recyclable.push_back(group);

	dialogFrame++;
	dialogTime--;

	return group;
}

void endDialogue()
{
	dialogues.position.keepLastKey(frame - 1.4f);
	offsetDialog -= 1;
	dialogY += 1;
	dialogues.position.addKey({ frame - 1.0f, {0, offsetDialog + 0.5f} });
}

void fit(vec2 s, kvec2& ks, kvec2& kp)
{
	float ratio = s.x / s.y;
	vec2 size = { 1, 1 };
	switch (pref.fit)
	{
	case pref.Auto:
		size = vec2{ ratio, 1.0f } * (view.aspectRatio / max(ratio, view.aspectRatio));
		break;
	case pref.Width:
		size = vec2{ ratio, 1.0f } * (view.aspectRatio / ratio);
		break;
	case pref.Fill:
		size = vec2{ view.aspectRatio, 1.0f };
		break;
	case pref.Height:
		size = vec2{ ratio, 1.0f };
		break;
	}

	ks = size;
	kp = vec2{ view.aspectRatio, 1 } * 0.5f - size * 0.5f;
}

//void setupGroup(FrameGroup& group)
//{
//	int begin = frame, len = pref.spanning;
//	if (pref.fade)
//	{
//		begin--; len++;
//	}
//
//	group.setDuration(len);
//
//	if (pref.over)
//		dialogues.addGroup(group, begin);
//	else
//		balloons.addGroup(group, begin);
//
//	if (!pref.over && !pref.under)
//		frame += len;
//}

FrameGroup* createImage(Texture2D& tex)
{
	auto img = new vg::Image;
	img->texture = &tex;
	fit(tex.size(), img->size, img->position);

	auto group = new FrameGroup{ img };
	group->setDuration(pref.spanning);

	if (pref.over)
		dialogues.addGroup(*group, frame);
	else
		balloons.addGroup(*group, frame);
	if (!pref.over && !pref.under)
		frame += pref.spanning;

	recyclable.push_back(img);
	recyclable.push_back(group);

	pref = ElemPref();

	return group;
}

FrameGroup* createVideo(const string& path)
{
	auto prov = VideoProvider::fromFile(path);
	auto vid = new vg::Video(prov);
	vid->presentationMode = true;

	fit(prov->size(), vid->size, vid->position);

	auto group = new FrameGroup{ vid };
	group->setDuration(pref.spanning);

	if (pref.over)
		dialogues.addGroup(*group, frame);
	else
		balloons.addGroup(*group, frame);
	if (!pref.over && !pref.under)
		frame += pref.spanning;

	recyclable.push_back(vid);
	recyclable.push_back(prov);
	recyclable.push_back(group);

	pref = ElemPref();

	return group;
}

FrameGroup* createBlank()
{
	auto group = new FrameGroup;
	group->setDuration(pref.spanning);

	balloons.addGroup(*group, frame);
	frame++;

	recyclable.push_back(group);

	pref = ElemPref();

	return group;
}

FrameGroup* createSound(string path)
{
	auto wave = audio::Waveform::fromOGG(path);
	auto prim = new audio::WaveformPrimitive(*wave);
	prim->presentationMode = true;
	auto group = new FrameGroup{ prim };
	group->setDuration(pref.spanning);
	balloons.addGroup(*group, frame);
	//frame++;

	recyclable.push_back(prim);
	recyclable.push_back(group);
	recyclable.push_back(wave);

	pref = ElemPref();

	return group;
}

string w2s(const wstring& ws)
{
	char buf2[500];
	int len = ws.length();
	for (int i = 0; i < len; i++)
		buf2[i] = (char)ws[i];
	buf2[len] = '\0';
	return buf2;
}

void readPrefixes(wstring& line)
{
	int i = 0, d;
	while (line.length() > 0)
	{
		int buflen = line.length();
		const wchar_t* buffer = line.c_str();
		if (swscanf(buffer, L"Spanning %d, %n", &d, &i) == 1)
			pref.spanning = d;
		else if (wcsncmp(buffer, L"Back, ", 6) == 0)
			pref.under = true, i = 6;
		else if (wcsncmp(buffer, L"Overlay, ", 9) == 0)
			pref.over = true, i = 9;
		else if (wcsncmp(buffer, L"Fade, ", 6) == 0)
			pref.fade = true, i = 6;
		else if (wcsncmp(buffer, L"Fill, ", 6) == 0)
			pref.fit = pref.Fill, i = 6;
		else if (wcsncmp(buffer, L"Fit width, ", 11) == 0)
			pref.fit = pref.Width, i = 11;
		else if (wcsncmp(buffer, L"Fit height, ", 12) == 0)
			pref.fit = pref.Height, i = 12;
		else if (wcsncmp(buffer, L"Fit auto, ", 10) == 0)
			pref.fit = pref.Auto, i = 10;
		else break;
		line = line.substr(i);
	}
}

void readData()
{
	const color cs[5] = { 
		{0.35, 0.5, 0.55, 1}, 
		{0.16, 0.35, 0.16, 1}, 
		{0.3, 0.6, 0.4, 1}, 
		{0.55, 0.4, 0.55, 1},
		Colors::mediumPurple };

	wifstream stream;
	auto utf8 = new codecvt_utf8<wchar_t, 0x10ffff, std::consume_header>();
	stream.open("data.txt", std::ios::binary);
	stream.imbue(locale(stream.getloc(), utf8));
	while (true)
	{
		wstring line;
		getline(stream, line);
		if (line.length() > 0 && line[line.length() - 1] == L'\r')
			line = line.substr(0, line.length() - 1);
		readPrefixes(line);

		int buflen = line.length();
		const wchar_t* buffer = line.c_str();

		wchar_t ch; wchar_t str[500]; float r, g, b; int n;
		if (swscanf(buffer, L"%c = (%f, %f, %f) %[^\n]", &ch, &r, &g, &b, str) == 5)
		{
			UserHead h;
			h.color = { r, g, b, 1 };
			h.name = str;
			heads.insert(make_pair(ch, h));
			continue;
		}
		else if (swscanf(buffer, L"%c: %[^\n]", &ch, str) == 2)
			createBalloon(str, heads[ch].name, heads[ch].color);
		else if (swscanf(buffer, L"%cd: %[^\n]", &ch, str) == 2)
			createDialogue(str, heads[ch].color);
		else if (swscanf(buffer, L"%c %d: %[^\n]", &ch, &n, str) == 3)
			createBalloon(str, heads[ch].name, heads[ch].color, n);

		else if (buflen > 5 && wcsncmp(buffer, L"Img: ", 5) == 0)
			createImage(*createShared(w2s(line.substr(5))));
		else if (buflen > 5 && wcsncmp(buffer, L"Vid: ", 5) == 0)
			createVideo(w2s(line.substr(5)));
		else if (buflen > 5 && wcsncmp(buffer, L"Sfx: ", 5) == 0)
			createSound(w2s(line.substr(5)));
		else if (buflen > 5 && wcsncmp(buffer, L"Blank", 5) == 0)
			createBlank();
		else if (buflen >= 3 && wcsncmp(buffer, L"End", 3) == 0)
			endDialogue();

		wchar_t c; int d, w, h; wchar_t s[500];
		if (swscanf(buffer, L"Speed = %f", &presentationSpeed) == 1);
		else if (swscanf(buffer, L"Monitor = %d", &monitor) == 1);
		else if (swscanf(buffer, L"Resolution = %dx%d", &w, &h) == 2)
		{
			Context::setSize(w, h);
			view.fromContext();
		}

		if (stream.eof())
			break;
	}
	stream.close();
}

void main()
{
	/*try
	{*/
		anip::init(1024, 768);
		audio::Mixer::init();
		audio::Mixer::begin();

		view.fromContext();

		FontManager::loadFace("default", "droidsans.ttf");

		font = FontManager::create("default", { 30 });
		font.incref();

		Sequence main(FPS);
		composite.setDuration(500);
		main.addGroup(composite, 0);

		readData();

		main.setFrame(0);

		float targetFrame = 0, currentFrame = 0;
		float lastTime = glfwGetTime(), pageTime = lastTime;

		Context::show(monitor);
		while (!Context::shouldQuit())
		{
			Context::doEvents();
			Context::clear(Colors::black);
			main.render(view);

			Key k; ButtonAction b;
			if (Context::getKey(k, b) && b == ButtonAction::Down)
			{
				switch (k)
				{
				case Key::right:
					targetFrame++;
					break;
				case Key::left:
					targetFrame--;
					if (targetFrame < 0)
						targetFrame = 0;
					break;
				case Key::escape:
					targetFrame = -1;
				}
			}

			if (targetFrame < 0)
				break;

			main.nextPresentationFrame();

			if (currentFrame < targetFrame)
			{
				currentFrame += min(presentationSpeed, targetFrame - currentFrame);
				main.setFrame(currentFrame);
			}
			else if (currentFrame > targetFrame)
			{
				currentFrame -= min(presentationSpeed, currentFrame - targetFrame);
				main.setFrame(currentFrame);
			}

			while (glfwGetTime() - lastTime < 1 / FPS);
			lastTime = glfwGetTime();

			Context::update();
		}
	/*}
	catch (exception& ex)
	{
		cerr << ex.what();
	}*/

	audio::Mixer::end();
	for (auto i : recyclable)
		delete i;
	FontManager::finalize();
	anip::finalize();
}

//int main(int argc, char **argv)
//{
//	anip::init(1000, 800);
//
//	editor::Whiteboard* wb = new editor::Whiteboard(nullptr, 1000, 800);
//	
//	wb->mainLoop();
//
//	delete wb;
//
//	FontManager::finalize();
//	anip::finalize();
//
//	return 0;
//}
